﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace DimensionsHelper.Common.Metadata.Axis;

public enum AxisType
{
    /// <summary>
    ///     普通的轴表达式，使用花括号包裹，{e1, e2, ..}
    /// </summary>
    Normal,

    /// <summary>
    ///     轴表达式变量，axis({e1, e2, ...})
    /// </summary>
    Variable,

    /// <summary>
    ///     元数据轴表达式，axis("{e1, e2, ...}")
    /// </summary>
    Metadata
}


/// <summary>
///     轴表达式
/// </summary>
public class AxisExpression : IAxisExclusive
{
    private readonly HashSet<string> _excludeNames;
    internal readonly LinkedList<AxisElement> Elements;
    internal readonly int[] ElementsCount;
    internal readonly Dictionary<string, LinkedListNode<AxisElement>> NameToNode;

    public AxisExpression()
    {
        Elements = [];
        NameToNode = new Dictionary<string, LinkedListNode<AxisElement>>(StringComparer.OrdinalIgnoreCase);
        _excludeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        ElementsCount = new int[(int)AxisElementType.Ppt + 1];
        Type = AxisType.Normal;
        Factory = new AxisElementFactory(this);
    }


    /// <summary>
    ///     轴表达式类型
    /// </summary>
    public AxisType Type { get; set; }


    /// <summary>
    ///     返回表达式中的元素数量。
    /// </summary>
    public int Count => Elements.Count;


    /// <summary>
    ///     轴元素函数工厂
    /// </summary>
    public AxisElementFactory Factory { get; }


    /// <summary>
    ///     使用索引随机获取轴元素
    /// </summary>
    public AxisElement this[int index]
    {
        get
        {
            var node = Elements.First;

            while (index > 0 && node != null)
            {
                node = node.Next;
                index--;
            }

            if (node == null)
            {
                throw new IndexOutOfRangeException();
            }

            return node.Value;
        }
    }


    /// <summary>
    ///     将指定名称添加到排除列表中，添加名称时，如果当前列表不是空，
    ///     将删除已有名称。
    /// </summary>
    /// <param name="names"></param>
    public void ExcludeNames(params string[] names)
    {
        foreach (string name in names)
        {
            if (NameToNode.TryGetValue(name, out var node))
            {
                Elements.Remove(node);
                NameToNode.Remove(name);
            }

            _excludeNames.Add(name);
        }
    }


    /// <summary>
    ///     移除指定轴元素。
    /// </summary>
    public void Remove(AxisElement element)
    {
        if (string.IsNullOrEmpty(element.Name))
        {
            if (Elements.Remove(element))
            {
                element.ElementAdded -= OnElementAdded;
            }

            return;
        }

        if (!NameToNode.TryGetValue(element.Name!, out var node))
        {
            return;
        }

        Elements.Remove(node);
        NameToNode.Remove(element.Name!);
        element.ElementAdded -= OnElementAdded;
    }


    /// <summary>
    ///     向列表末尾添加一个新的已有轴元素，如果已有此轴元素，将删除重复元素并在末尾添加此轴元素。
    /// </summary>
    public void Append(AxisElement element)
    {
        if (!string.IsNullOrEmpty(element.Name) && _excludeNames.Contains(element.Name!))
        {
            return;
        }

        Remove(element);
        AddElement(element);
    }


    public IEnumerator<AxisElement> GetEnumerator()
    {
        return Elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    public override string ToString()
    {
        string expr = string.Join(", ", Elements.Select(e => e.ToString()));

        switch (Type)
        {
            case AxisType.Normal:
                return $"{{{expr}}}";
            case AxisType.Variable:
                return $"axis({{{expr}}})";
            case AxisType.Metadata:
                return $"axis(\"{{{expr}}}\")";
            default:
                return "";
        }
    }


#if NET8_OR_GREATER
        internal string GetElementName(AxisElementType type, string? name)
#else
    internal string GetElementName(AxisElementType type, string name)
#endif
    {
        if (string.IsNullOrEmpty(name))
        {
            ElementsCount[(int)type]++;
            return type + ElementsCount[(int)type].ToString();
        }

        if (NameToNode.ContainsKey(name))
        {
            throw new Exception($"名为'{name}'的轴元素已存在。");
        }

        return name;
    }


    private void OnElementAdded(AxisElement element)
    {
        ElementsCount[(int)element.Type]++;
    }


    private void AddElement(AxisElement element)
    {
        // 不添加 excludeNames 列表中的元素名
        if (!string.IsNullOrEmpty(element.Name) && _excludeNames.Contains(element.Name!))
        {
            return;
        }

        // 如果添加的是排除名称，添加到排除列表中
        if (element.IsExclude)
        {
            if (!string.IsNullOrEmpty(element.Name))
            {
                ExcludeNames(element.Name!);
            }

            return;
        }

        LinkedListNode<AxisElement> node = Elements.AddLast(element);

        if (!string.IsNullOrEmpty(element.Name))
        {
            // 如果同名分类已存在，使用后添加的替换已有的
            if (NameToNode.TryGetValue(element.Name!, out var listNode))
            {
                // 只有分类成员允许重复出现
                if (element.Type != AxisElementType.Category)
                {
                    throw new Exception($"名为'{element.Name}'的轴元素已存在。");
                }

                Elements.Remove(listNode);
                NameToNode[element.Name!] = node;
            }
            else
            {
                NameToNode.Add(element.Name!, node);
            }
        }

        ElementsCount[(int)element.Type]++;

        element.ElementAdded -= OnElementAdded;
        element.ElementAdded += OnElementAdded;
    }


    /// <summary>
    ///     向表达式末尾添加一个分类值并返回。
    /// </summary>
    /// <param name="category">分类元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendCategory(string category, string? description = null)
    {
        AxisElement categoryObj = AxisElementFactory.CreateCategory(category, description);
        AddElement(categoryObj);
        return categoryObj;
    }


    /// <summary>
    ///     向表达式末尾添加一个分类值区间并返回。
    /// </summary>
    /// <param name="lower">区间下限</param>
    /// <param name="upper">区间上限</param>
    public AxisElement AppendCategoryRange(string? lower = null, string? upper = null)
    {
        AxisElement range = AxisElementFactory.CreateCategoryRange(lower, upper);
        Elements.AddLast(range);
        return range;
    }


    /// <summary>
    ///     向表达式末尾追加一个新的文本行元素。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendText(string name, string? description = null)
    {
        AxisElement textElement = Factory.CreateText(name, description);
        AddElement(textElement);
        return textElement;
    }


    /// <summary>
    ///     向表达式末尾追加一个基数元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="expr">基数表达式</param>
    public AxisElement AppendBase(string name, string? description = null, string? expr = null)
    {
        AxisElement baseElement = Factory.CreateBase(name, description, expr);
        AddElement(baseElement);
        return baseElement;
    }


    /// <summary>
    ///     向表达式末尾追加一个未加权基数的元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="expr">未加权基数表达式</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendUnweightedBase(string? name = null, string? description = null, string? expr = null)
    {
        AxisElement unweighted = Factory.CreateUnweightedBase(name, description, expr);
        AddElement(unweighted);
        return unweighted;
    }


    /// <summary>
    ///     向表达式末尾追加一个有效基数元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendEffectiveBase(string? name = null, string? description = null)
    {
        AxisElement effective = Factory.CreateEffectiveBase(name, description);
        AddElement(effective);
        return effective;
    }


    /// <summary>
    ///     向表达式末尾追加一个表达式元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="expr">表达式字符串</param>
    public AxisElement AppendExpression(string name, string description, string expr)
    {
        AxisElement expression = Factory.CreateExpression(name, description, expr);
        AddElement(expression);
        return expression;
    }


    /// <summary>
    ///     向表达式末尾追加一个均值元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendMean(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateMean(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个标准误差元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendStdErr(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateStdErr(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个标准偏差元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendStdDev(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateStdDev(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个样本方差元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendSampVar(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateSampVar(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个最小值元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendMin(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateMin(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个最大值元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendMax(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateMax(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个众数元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendMode(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateMode(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个中位数元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendMedian(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateMedian(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个求和元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">变量名</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendSum(string? name = null, string? description = null, string? varName = null,
        string? filter = null)
    {
        AxisElement element = Factory.CreateSum(name, description, varName, filter);
        AddElement(element);
        return element;
    }


    /// <summary>
    ///     向表达式末尾追加一个小计元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendSubtotal(string? name = null, string? description = null)
    {
        AxisElement subtotal = Factory.CreateSubTotal(name, description);
        AddElement(subtotal);
        return subtotal;
    }


    /// <summary>
    ///     向表达式末尾追加一个总计元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendTotal(string? name = null, string? description = null)
    {
        AxisElement total = Factory.CreateTotal(name, description);
        AddElement(total);
        return total;
    }


    /// <summary>
    ///     向表达式末尾追加一个合并并出示的元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="items">添加的子元素列表</param>
    public AxisElement AppendNet(string? name = null, string? description = null,
        IEnumerable<AxisElement>? items = null)
    {
        AxisElement net = Factory.CreateNet(name, description, items);
        AddElement(net);
        return net;
    }


    /// <summary>
    ///     向表达式末尾追加一个合并并不出示的元素并返回。
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="items">添加的子元素列表</param>
    public AxisElement AppendCombine(string? name = null, string? description = null,
        IEnumerable<AxisElement>? items = null)
    {
        AxisElement combine = Factory.CreateCombine(name, description, items);
        AddElement(combine);
        return combine;
    }


    /// <summary>
    ///     向表达式末尾添加一个百分位数元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    /// <param name="varName">数值变量名</param>
    /// <param name="cutOffValue">百分位数</param>
    /// <param name="filter">筛选器表达式</param>
    public AxisElement AppendPercentile(string name, string description, string varName, double cutOffValue = 50d,
        string? filter = null)
    {
        AxisElement percentile = Factory.CreatePercentile(name, description, varName, cutOffValue, filter);
        AddElement(percentile);
        return percentile;
    }


    /// <summary>
    ///     向表达式末尾添加一个ntd元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendNtd(string? name = null, string? description = null)
    {
        AxisElement ntd = Factory.CreateNtd(name, description);
        AddElement(ntd);
        return ntd;
    }


    /// <summary>
    ///     向表达式末尾添加一个ppt元素并返回
    /// </summary>
    /// <param name="name">元素名</param>
    /// <param name="description">元素文本描述</param>
    public AxisElement AppendPpt(string? name = null, string? description = null)
    {
        AxisElement ppt = Factory.CreatePpt(name, description);
        AddElement(ppt);
        return ppt;
    }


    /// <summary>
    ///     解析轴表达式并返回
    /// </summary>
    /// <exception cref="Exception"></exception>
    public static AxisExpression Parse(string expression)
    {
        AxisExpression axis;
        string text;

        if (expression.StartsWith("{") && expression.EndsWith("}"))
        {
            axis = new AxisExpression { Type = AxisType.Normal };
            text = expression.Substring(1, expression.Length - 2);
        }
        else if (expression.StartsWith("axis({", StringComparison.OrdinalIgnoreCase)
                 && expression.EndsWith("})"))
        {
            axis = new AxisExpression { Type = AxisType.Variable };
            text = expression.Substring(6, expression.Length - 8);
        }
        else
        {
            throw new Exception("无效的轴表达式。");
        }

        var parser = new AxisParser(axis, text);
        parser.ReadAll(axis);

        return axis;
    }
}